/**
 * @fileoverview Contains all Jabber/XMPP packet related classes.
 * @author Stefan Strigler steve@zeank.in-berlin.de
 */

/*exported JSJaCPacket, JSJaCPresence, JSJaCIQ, JSJaCMessage */
import {XmlUtil} from "./XmlUtil"
import {JSJaCJID} from './JSJaCJID'
import {STANZA_ERROR} from './STANZA_ERROR'
import {JSJaCBuilder} from './JSJaCBuilder'
import * as JSJaCConstants from './JSJaCConstants'
import {IXMLDocument} from "./IXMLDocument"
import {IElement} from "./IElement"

const JSJACPACKET_USE_XMLNS:Boolean = true;

/**
 * Creates a new packet with given root tag name (for internal use)
 * @class Somewhat abstract base class for all kinds of specialised packets
 * @param {String} name The root tag name of the packet
 * (i.e. one of 'message', 'iq' or 'presence')
 */
export  abstract class JSJaCPacket {
    private doc:IXMLDocument;
    constructor(public name:string) {
        if (JSJACPACKET_USE_XMLNS) {
            this.doc=XmlUtil.create(name, JSJaCConstants.NS_CLIENT)
        } else {
            this.doc=XmlUtil.create(name)
        }
    }
    /**
     * Gets the type (name of root element) of this packet, i.e. one of
     * 'presence', 'message' or 'iq'
     * @return {string} The top level tag name.
     */
    pType():string {
        return this.name;
    }
    /**
     * Gets the associated Document for this packet.
     * @see {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document | Document}
     * @returns {Document}
     */
    getDoc():IXMLDocument{
      return this.doc;
    }
    setDoc(doc:IXMLDocument):void {
      this.doc=doc
    }
    /**
     * Gets the root node of this packet
     * @see {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 | Node}
     * @return {Node}
     */
    getNode ():IElement|null {
      if (this.getDoc())
        return this.getDoc().getRootElement();
      else
        return null;
    }

    /**
     * Sets the 'to' attribute of the root node of this packet
     * @param {String} [to] A string representing a jid sending this packet to. If omitted the property will be deleted thus sending to service rather than dedicated recipient.
     * @return {JSJaCPacket} this
     */
    setTo(to:string=undefined):JSJaCPacket {
      if(!to)
         this.getNode().removeAttribute('to')
      else 
         this.getNode().setAttribute('to',to);
      return this;
    };

    /**
     * Sets the 'from' attribute of the root node of this
     * packet. Usually this is not needed as the server will take care
     * of this automatically.
     * @param {string} [from] A string representing the jid of the sender of this packet.
     * @deprecated
     * @return {JSJaCPacket} this
     */
    setFrom(from:string=undefined):JSJaCPacket {
      if(!from)
         this.getNode().removeAttribute('from')
      else
         this.getNode().setAttribute('from',from);
      return this;
    };
    /**
     * Sets 'id' attribute of the root node of this packet.
     * @param {string} id The id of the packet.
     * @return {JSJaCPacket} this
     */
    setID (id:string=undefined):JSJaCPacket{
      if(!id)
        this.getNode().removeAttribute('id')
      else
        this.getNode().setAttribute('id',id);
      return this;
    };
    /**
     * Sets the 'type' attribute of the root node of this packet.
     * @param {string} type The type of the packet.
     * @return {JSJaCPacket} this
     */
    setType(type:string=undefined):JSJaCPacket {
      if(!type)
        this.getNode().removeAttribute('type')
      else
        this.getNode().setAttribute('type',type);
      return this;
    };
    /**
     * Sets 'xml:lang' for this packet
     * @param {string} xmllang The xml:lang of the packet.
     * @return {JSJaCPacket} this
     */
    setXMLLang(xmllang:string=undefined):JSJaCPacket {
      if (!xmllang)
        this.getNode().removeAttribute('xml:lang');
      else
        this.getNode().setAttribute('xml:lang',xmllang);
      return this;
    };
    
    /**
     * Gets the 'to' attribute of this packet
     * @return {string}
     */
    getTo():string {
      return this.getNode().getAttribute('to');
    };
    /**
     * Gets the 'from' attribute of this packet.
     * @return {string}
     */
    getFrom():string {
      return this.getNode().getAttribute('from');
    };
    /**
     * Gets the 'to' attribute of this packet as a JSJaCJID object
     * @return {JSJaCJID}
     */
    getToJID():JSJaCJID {
      return new JSJaCJID(this.getTo());
    };
    /**
     * Gets the 'from' attribute of this packet as a JSJaCJID object
     * @return {JSJaCJID}
     */
    getFromJID():JSJaCJID {
      return new JSJaCJID(this.getFrom());
    };
    /**
     * Gets the 'id' of this packet
     * @return {string}
     */
    getID() {
      return this.getNode().getAttribute('id');
    };
    /**
     * Gets the 'type' of this packet
     * @return {string}
     */
    getType() {
      return this.getNode().getAttribute('type');
    };
    /**
     * Gets the 'xml:lang' of this packet
     * @return {string}
     */
    getXMLLang() {
      return this.getNode().getAttribute('xml:lang');
    };
    /**
     * Gets the 'xmlns' (xml namespace) of the root node of this packet
     * @return {string}
     */
    getXMLNS() {
      return this.getNode().getNamespaceURI() || this.getNode().getAttribute('xmlns');
    };
    /**
     * Gets a child element of this packet. If no params given returns first child.
     * @see {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 | Node}
     * @param {string} name Tagname of child to retrieve. Use '*' to match any tag. [optional]
     * @param {string} ns   Namespace of child. Use '*' to match any ns.[optional]
     * @return {Node} The child node, null if none found
     */
    getChild(name?:string, ns?:string):IElement {
      if (!this.getNode()) {
        return null;
      }
      name = name || '*';
      ns = ns || '*';
    
      let tagNameNsArray=this.getNode().getElementsByTagNameNS(ns,name)
      if (tagNameNsArray.length>0) {
        return tagNameNsArray[0];
      }
      // fallback
      let nodes = this.getNode().getElementsByTagName(name);
      if (ns != '*') {
        for (var i=0; i<nodes.length; i++) {
          if (nodes[i].getNamespaceURI() == ns || nodes[i].getAttribute('xmlns') == ns) {
            return nodes[i];
          }
        }
      } else {
        return nodes[0];
      }
      return null; // nothing found
    };
    
    /**
     * Gets the node value of a child element of this packet.
     * @param {string} name Tagname of child to retrieve.
     * @param {string} ns   Namespace of child
     * @return {string} The value of the child node, empty string if none found
     */
    getChildVal(name:string, ns?:string):string {
      let node = this.getChild(name, ns);
      var ret = '';
      if (node && node.hasChildNodes()) {
        // concatenate all values from childNodes
        ret=node.getAllChildNodeValue()
      }
      return ret;
    };
    abstract clone():JSJaCPacket; 
    /**
     * Returns a copy of this node
     * @return {JSJaCPacket} a copy of this node
     */
    /*
    clone():JSJaCPacket {
      return JSJaCPacketUtil.wrapNode(this.getNode());
    };
    */
    /**
     * Checks if packet is of type 'error'
     * @return {boolean} 'true' if this packet is of type 'error', 'false' otherwise
     */
    isError():boolean {
      return this.getType() == 'error';
    };
    
    /**
     * Returns an error condition reply according to {@link http://xmpp.org/extensions/xep-0086.html | XEP-0086}. Creates a clone of the calling packet with senders and recipient exchanged and error stanza appended.
     * @param {STANZA_ERROR} stanza_error an error stanza containing error cody, type and condition of the error to be indicated
     * @return {JSJaCPacket} an error reply packet
     */
    errorReply(stanza_error:STANZA_ERROR):JSJaCPacket {
      var rPacket = this.clone();
      rPacket.setTo(this.getFrom());
      rPacket.setFrom();
      rPacket.setType('error');
    
      rPacket.appendNode('error',
                         {code: stanza_error.code, type: stanza_error.type},
                         [[stanza_error.cond, {xmlns: JSJaCConstants.NS_STANZAS}]]);
    
      return rPacket;
    };
    
    /**
     * Returns a string representation of the raw xml content of this packet.
     * @return {string} deserialized xml packet
     */
    xml():string {
      /**
       * 
      var r = (new XMLSerializer()).serializeToString(this.getNode());
      if (typeof(r) == 'undefined')
        r = (new XMLSerializer()).serializeToString(this.doc); // oldschool
       */
      return this.getNode().toXmlStr()
    }
    /**
     * Set node value of a child node
     * @private
     */
    protected _setChildNode (nodeName:string, nodeValue:string):IElement{
      let aNode = this.getChild(nodeName);
      if (aNode) {
         aNode.changeOrAddNodeValue(nodeValue)
      } else {
        try {
          aNode = this.getDoc().createElementNS(this.getNode().getNamespaceURI(),
                                                nodeName);
        } catch (ex) {
          aNode = this.getDoc().createElement(nodeName);
        }
        this.getNode().appendChild(aNode);
        aNode.changeOrAddNodeValue(nodeValue);
      }
      return aNode;
    };
    /**
     * Appends node created by buildNode to this packets parent node.
     * @param {Node} element The node to append or
     * @param {string} element A name plus an object hash with attributes (optional) plus an array of childnodes (optional)
     * @see #buildNode
     * @return {JSJaCPacket} This packet
     * @see {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 | Node}
     */
    /**
     * 
     * @param element The node to append or
     * @param attribute  A name plus an object hash with attributes (optional) plus an array of childnodes (optional)
     * @param childrens 
     * @returns 
     */
    appendNode(element:IElement|string,attribute:any,childrens:any):JSJaCPacket {
      if (typeof element=='string') { // seems to be a prebuilt node
        this.getNode().appendChild(this.buildNode(element,
          attribute,
          childrens,
          this.getNode().getNamespaceURI()));
      } else { // build node
        this.getNode().appendChild(element);
      }
      return this;
    };
    /**
     * Builds a node using {@link http://wiki.script.aculo.us/scriptaculous/show/Builder | script.aculo.us' Dom Builder} notation.
     * This code is taken from {@link http://wiki.script.aculo.us/scriptaculous/show/Builder | script.aculo.us' Dom Builder} and has been modified to suit our needs.<br/>
     * The original parts of the code do have the following copyright
     * and license notice:<br/>
     * Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us,
     * http://mir.acu lo.us) <br/>
     * script.aculo.us is freely distributable under the terms of an
     * MIT-style licen se.  // For details, see the script.aculo.us web
     * site: http://script.aculo.us/<br>
     * @author Thomas Fuchs
     * @author Stefan Strigler
     * @return {Node} The newly created node
     * @see {@link http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 | Node}
     */
    buildNode(elementName:string,attribute?:any,childrens?:any,ns?:string):IElement {
      return JSJaCBuilder.buildNode(this.getDoc(),
                                    elementName,
                                    attribute,
                                    childrens,
                                    ns);
    };

    /**
     * import node into this packets document
     * 
    public _importNode (node: IElement, allChildren: boolean):IElement {
      return node.clone(allChildren)
    }
     */

}